/*-------------------------------------------------------------------------
 *
 * pg_regress_ecpg --- ecpg回归测试驱动程序
 *
 * This is a C implementation of the previous shell script for running
 * the regression tests, and should be mostly compatible with it.
 * Initial author of C translation: Magnus Hagander
 *
 * This code is released under the terms of the PostgreSQL License.
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/interfaces/ecpg/test/pg_regress_ecpg.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include "pg_regress.h"
#include "common/string.h"
#include "lib/stringinfo.h"


/*
 * 创建源文件的过滤副本，移除出现在 #line 指令中的任何路径；例如，将
 * #line x "./../bla/foo.h" 替换为 #line x "foo.h"。
 * 这是必要的，因为路径部分可能根据编译器、平台、构建选项等而有所不同。
 */
static void fc_ecpg_filter_source(const char *fc_sourcefile, const char *fc_outfile)
{
	FILE	   *fc_s,
			   *fc_t;
	StringInfoData fc_linebuf;

	fc_s = fopen(fc_sourcefile, "r");
	if (!fc_s)
	{
		fprintf(stderr, "Could not open file %s for reading\n", fc_sourcefile);
		exit(2);
	}
	fc_t = fopen(fc_outfile, "w");
	if (!fc_t)
	{
		fprintf(stderr, "Could not open file %s for writing\n", fc_outfile);
		exit(2);
	}

	initStringInfo(&fc_linebuf);

	while (pg_get_line_buf(fc_s, &fc_linebuf))
	{
		/* 检查开头是否有 "#line " */
		if (strstr(fc_linebuf.data, "#line ") == fc_linebuf.data)
		{
			char	   *fc_p = strchr(fc_linebuf.data, '"');
			int			fc_plen = 1;

			while (*fc_p && (*(fc_p + fc_plen) == '.' || strchr(fc_p + fc_plen, '/') != NULL))
			{
				fc_plen++;
			}
			/* plen 的值比 . 和 / 字符的数量多一个 */
			if (fc_plen > 1)
			{
				memmove(fc_p + 1, fc_p + fc_plen, strlen(fc_p + fc_plen) + 1);
				/* 我们不想去修正 linebuf.len */
			}
		}
		fputs(fc_linebuf.data, fc_t);
	}

	pfree(fc_linebuf.data);
	fclose(fc_s);
	fclose(fc_t);
}

/*
 * 移除测试结果文件中连接失败错误消息的细节，
 * 因为目标主机/路径名和/或端口可能会变化。就地重写结果文件。
 *
 * 在某个时刻，将其与 ecpg_filter_source 统一可能会很有趣，
 * 但构建一个通用的模式匹配器并不好玩，而且引入
 * 对外部的依赖似乎也不可取。
 */
static void fc_ecpg_filter_stderr(const char *fc_resultfile, const char *fc_tmpfile)
{
	FILE	   *fc_s,
			   *fc_t;
	StringInfoData fc_linebuf;

	fc_s = fopen(fc_resultfile, "r");
	if (!fc_s)
	{
		fprintf(stderr, "Could not open file %s for reading\n", fc_resultfile);
		exit(2);
	}
	fc_t = fopen(fc_tmpfile, "w");
	if (!fc_t)
	{
		fprintf(stderr, "Could not open file %s for writing\n", fc_tmpfile);
		exit(2);
	}

	initStringInfo(&fc_linebuf);

	while (pg_get_line_buf(fc_s, &fc_linebuf))
	{
		char	   *fc_p1 = strstr(fc_linebuf.data, "connection to server ");

		if (fc_p1)
		{
			char	   *fc_p2 = strstr(fc_p1, "failed: ");

			if (fc_p2)
			{
				memmove(fc_p1 + 21, fc_p2, strlen(fc_p2) + 1);
				/* 我们不想去修正 linebuf.len */
			}
		}
		fputs(fc_linebuf.data, fc_t);
	}

	pfree(fc_linebuf.data);
	fclose(fc_s);
	fclose(fc_t);
	if (rename(fc_tmpfile, fc_resultfile) != 0)
	{
		fprintf(stderr, "Could not overwrite file %s with %s\n",
				fc_resultfile, fc_tmpfile);
		exit(2);
	}
}

/*
 * 为指定文件（包括重定向）启动一个 ecpg 测试进程，
 * 并返回进程 ID
 */

static PID_TYPE fc_ecpg_start_test(const char *fc_testname,
				_stringlist **fc_resultfiles,
				_stringlist **fc_expectfiles,
				_stringlist **fc_tags)
{
	PID_TYPE	pid;
	char		fc_inprg[MAXPGPATH];
	char		fc_insource[MAXPGPATH];
	StringInfoData fc_testname_dash;
	char		fc_outfile_stdout[MAXPGPATH],
				fc_expectfile_stdout[MAXPGPATH];
	char		fc_outfile_stderr[MAXPGPATH],
				fc_expectfile_stderr[MAXPGPATH];
	char		fc_outfile_source[MAXPGPATH],
				fc_expectfile_source[MAXPGPATH];
	char		fc_cmd[MAXPGPATH * 3];
	char	   *fc_appnameenv;

	snprintf(fc_inprg, sizeof(fc_inprg), "%s/%s", inputdir, fc_testname);
	snprintf(fc_insource, sizeof(fc_insource), "%s.c", fc_testname);

	/* 制作一个测试名称的版本，其中将斜杠替换为破折号 */
	initStringInfo(&fc_testname_dash);
	appendStringInfoString(&fc_testname_dash, fc_testname);
	for (char *fc_c = fc_testname_dash.data; *fc_c != '\0'; fc_c++)
	{
		if (*fc_c == '/')
			*fc_c = '-';
	}

	snprintf(expectfile_stdout, sizeof(expectfile_stdout),
			 "%s/expected/%s.stdout",
			 outputdir, testname_dash.data);
	snprintf(expectfile_stderr, sizeof(expectfile_stderr),
			 "%s/expected/%s.stderr",
			 outputdir, testname_dash.data);
	snprintf(expectfile_source, sizeof(expectfile_source),
			 "%s/expected/%s.c",
			 outputdir, testname_dash.data);

	snprintf(outfile_stdout, sizeof(outfile_stdout),
			 "%s/results/%s.stdout",
			 outputdir, testname_dash.data);
	snprintf(outfile_stderr, sizeof(outfile_stderr),
			 "%s/results/%s.stderr",
			 outputdir, testname_dash.data);
	snprintf(outfile_source, sizeof(outfile_source),
			 "%s/results/%s.c",
			 outputdir, testname_dash.data);

	add_stringlist_item(resultfiles, fc_outfile_stdout);
	add_stringlist_item(expectfiles, fc_expectfile_stdout);
	add_stringlist_item(tags, "stdout");

	add_stringlist_item(resultfiles, fc_outfile_stderr);
	add_stringlist_item(expectfiles, fc_expectfile_stderr);
	add_stringlist_item(tags, "stderr");

	add_stringlist_item(resultfiles, fc_outfile_source);
	add_stringlist_item(expectfiles, fc_expectfile_source);
	add_stringlist_item(tags, "source");

	fc_ecpg_filter_source(fc_insource, fc_outfile_source);

	snprintf(fc_cmd, sizeof(fc_cmd),
			 "\"%s\" >\"%s\" 2>\"%s\"",
			 fc_inprg,
			 fc_outfile_stdout,
			 fc_outfile_stderr);

	fc_appnameenv = psprintf("ecpg/%s", fc_testname_dash.data);
	setenv("PGAPPNAME", fc_appnameenv, 1);
	free(fc_appnameenv);

	pid = spawn_process(cmd);

	if (pid == INVALID_PID)
	{
		fprintf(stderr, _("could not start process for test %s\n"),
				fc_testname);
		exit(2);
	}

	unsetenv("PGAPPNAME");

	free(fc_testname_dash.data);

	return pid;
}

static void fc_ecpg_postprocess_result(const char *fc_filename)
{
	int			fc_nlen = strlen(fc_filename);

	/* 目前只需要过滤 stderr 文件 */
	if (fc_nlen > 7 && strcmp(fc_filename + fc_nlen - 7, ".stderr") == 0)
	{
		char	   *fc_tmpfile = psprintf("%s.tmp", fc_filename);

		fc_ecpg_filter_stderr(fc_filename, fc_tmpfile);
		pfree(fc_tmpfile);
	}
}

static void fc_ecpg_init(int fc_argc, char *fc_argv[])
{
	/* 目前这里没有什么可做的 */
}

int main(int fc_argc, char *fc_argv[])
{
	return regression_main(fc_argc, fc_argv,
						   fc_ecpg_init,
						   ecpg_start_test,
						   fc_ecpg_postprocess_result);
}
